package cn.zhxu.crud.service;

import cn.zhxu.crud.*;
import cn.zhxu.crud.entity.Del;
import jakarta.transaction.Transactional;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.CrudRepository;

import java.lang.reflect.ParameterizedType;
import java.util.Objects;
import java.util.Optional;

/**
 * 基础服务层，封装了保存、删除操作
 * @param <Rep> 仓库类型
 * @param <T> 实体类类型
 * @param <ID> ID 类型
 * @param <Input> 输入类型
 */
public class BaseService<Rep extends CrudRepository<T, ID>, T extends Copyer<T>, ID, Input> {

    private static ServiceHook hook = null;

    public static void registerHook(ServiceHook hook) {
        ServiceHook h = BaseService.hook;
        if (h != null) {
            BaseService.hook = new ServiceHooks(h, Objects.requireNonNull(hook));
        } else {
            BaseService.hook = Objects.requireNonNull(hook);
        }
    }

    private Rep repository;
    private final Class<T> domainClass;

    @SuppressWarnings("unchecked")
    public BaseService() {
        var types = ((ParameterizedType) this.getClass().getGenericSuperclass())
                .getActualTypeArguments();
        this.domainClass = (Class<T>) types[1];
    }

    @Autowired
    @SuppressWarnings("all")
    public void setRepository(Rep repository) {
        this.repository = repository;
    }

    public Rep repository() {
        return repository;
    }

    public Optional<T> optional(ID id) {
        return repository.findById(id)
                .filter(d -> {
                    if (hook != null && !hook.onFindById(d)) {
                        return false;
                    }
                    return !(d instanceof Del del) || !del.isDeleted();
                });
    }

    public T nullable(ID id) {
        return optional(id).orElse(null);
    }

    public T require(ID id) {
        return optional(id).orElseThrow(() -> new NotExistsException("No " + domainClass.getSimpleName() + ": " + id));
    }

    /**
     * 使用关键字段值为保存操作查询实体类
     * @param input Input
     * @return T
     */
    protected T forSaveByKeyValue(Input input) {
        return null;
    }

    /**
     * @param input Input
     * @return 关键字段，已存在时的提示信息
     */
    protected String keyValueExistsTip(Input input) {
        return "键值已存在";
    }

    protected T newInstance() {
        try {
            return domainClass.getDeclaredConstructor().newInstance();
        } catch (ReflectiveOperationException e) {
            throw new IllegalStateException(e);
        }
    }

    @Transactional
    @SuppressWarnings("unchecked")
    public Saved<T> save(Input input) {
        T old = null;
        var entity = forSaveByKeyValue(input);
        if (entity == null) {
            if (input instanceof Identifier<?> identifier && identifier.getId() != null) {
                entity = require((ID) identifier.getId());
                old = entity.copy();
            } else {
                entity = newInstance();
            }
        } else if (input instanceof Identifier<?> iid
                && entity instanceof Identifier<?> did
                && !Objects.equals(iid.getId(), did.getId())) {
            throw new KeyExistsException(keyValueExistsTip(input));
        } else {
            old = entity.copy();
        }
        if (old != null) {
            if (hook != null) {
                hook.onUpdating(entity, input);
            }
            // 更新校验
            onUpdating(entity, input);
        } else {
            if (hook != null) {
                hook.onCreating(entity, input);
            }
            onCreating(entity, input);
        }
        // 忽略 ID 字段
        BeanUtils.copyProperties(input, entity, idNames());
        if (hook != null) {
            hook.onSaving(entity, input);
        }
        onSaving(entity, input);
        entity = repository.save(entity);
        if (hook != null) {
            hook.onSaved(entity, input);
        }
        onSaved(entity, input);
        return new Saved<>(old, entity);
    }

    /**
     * 获取 ID 字段名称
     * @return ID 字段名称
     * @since v0.0.6
     */
    public String[] idNames() {
        return new String[] { "id" };
    }

    /**
     * 创建前调用
     * @param input 创建输入
     */
    protected void onCreating(T entity, Input input) { }

    /**
     * 更新前调用
     * @param entity 更新前的实体
     * @param input 更新输入
     */
    protected void onUpdating(T entity, Input input) { }

    /**
     * 保存前调用
     * @param entity 带保存的实体
     * @param input 带保存的输入
     */
    protected void onSaving(T entity, Input input) { }

    /**
     * 保存后调用
     * @param entity 已保存的实体
     * @param input 带保存的输入
     */
    protected void onSaved(T entity, Input input) { }

    @Transactional
    public T delete(ID id) {
        var opt = repository.findById(id);
        if (opt.isEmpty()) {
            return null;
        }
        var entity = opt.get();
        if (hook != null) {
            hook.onDeleting(entity);
        }
        onDeleting(entity);
        if (entity instanceof Del del) {
            del.setDeleted(true);
            repository.save(entity);
        } else {
            repository.delete(entity);
        }
        if (hook != null) {
            hook.onDeleted(entity);
        }
        onDeleted(entity);
        return entity;
    }

    /**
     * 删除前调用
     * @param entity 待删除的实体
     */
    protected void onDeleting(T entity) {}

    /**
     * 删除后调用
     * @param entity 已删除的实体
     */
    protected void onDeleted(T entity) {}

}
